﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using XcoAppSpaces.Contracts.Service;
using XcoAppSpaces.Contracts.Security;
using XcoAppSpaces.Contracts.Logging;
using XcoAppSpaces.Core.Ports;

namespace XcoAppSpaces.Security.Basic
{
	class WorkerPermissionInfo
	{
		public List<string> WorkerRoles { get; set; }
		public Dictionary<Type, List<string>> PortRoles { get; set; }

		public WorkerPermissionInfo(IPort worker, IEnumerable<XcoServiceAttributeInfo> workerAttributes, IXcoLogger log)
		{
			WorkerRoles = new List<string>();
			PortRoles = new Dictionary<Type, List<string>>();
			//get worker roles
			IEnumerable<XcoServiceAttributeInfo> securityAttrs = workerAttributes.Where(a => a.Attr is XcoSecurityPermissionAttribute);
			WorkerRoles.AddRange(securityAttrs.Where(a => a.WorkerMessageType == null).Select(a => ((XcoSecurityPermissionAttribute)a.Attr).Rolename));
			//get port roles
			InitializePortRoles(worker);
			foreach (XcoServiceAttributeInfo info in securityAttrs.Where(a => a.WorkerMessageType != null))
			{
				if (!PortRoles.ContainsKey(info.WorkerMessageType))
				{
					log.Error("Attribute for message type found that is not part of the worker: " + info.WorkerMessageType);
					continue;
				}
				string rolename = ((XcoSecurityPermissionAttribute) info.Attr).Rolename;
				if (!PortRoles[info.WorkerMessageType].Contains(rolename))
					PortRoles[info.WorkerMessageType].Add(rolename);
			}
		}

		public List<Type> GetAllowedMessageTypes(User user)
		{
			//check which messages typs are allowed for the given user to be posted to the worker
			var allowed = new List<Type>();
			if (user == null)
			{
				//no user: worker only allowed if worker doesn't define any roles
				if (WorkerRoles.Count > 0)
					return null;
				foreach (Type portType in PortRoles.Keys)
				{
					if (PortRoles[portType].Count == 0)
						allowed.Add(portType);
				}
			}
			else
			{
				bool userHasFullAccess = false;
				foreach (string role in user.Roles)
				{
					if (WorkerRoles.Contains(role))
					{
						//user has global worker role: add all port types as allowed
						foreach (Type portType in PortRoles.Keys)
						{
							allowed.Add(portType);
						}
						userHasFullAccess = true;
						break;
					}
				}
				bool noWorkerRolesDefined = (WorkerRoles.Count == 0);
				if (!userHasFullAccess)
				{
					foreach (Type portType in PortRoles.Keys)
					{
						//add port if it defines one of the user's roles, or if it is open for everyone (when no roles this port and the worker are defined)
						if ((PortRoles[portType].Count == 0 && noWorkerRolesDefined))
						{
							allowed.Add(portType);
						}
						else
						{
							foreach (string role in user.Roles)
							{
								if (PortRoles[portType].Contains(role))
								{
									allowed.Add(portType);
									break;
								}
							}
						}
					}
				}
			}
			return allowed;
		}

		private void InitializePortRoles(IPort worker)
		{
			//initializes the list of port roles with all port message types of the worker
			if (worker is IPortSet)
			{
				foreach (IPort port in ((IPortSet)worker).Ports)
				{
					PortRoles.Add(GetMessageType(port.GetType()), new List<string>());
				}
			}
			else
			{
				PortRoles.Add(GetMessageType(worker.GetType()), new List<string>());
			}
		}

		private Type GetMessageType(Type portType)
		{
			//gets the message type from the given port type (which must be Port<T> or a Subtype of Port<T>)
			if (portType != null)
			{
				if (IsPortType(portType))
					return portType.GetGenericArguments()[0];
				return GetMessageType(portType.BaseType);
			}
			return null;
		}

		private bool IsPortType(Type type)
		{
			return (type != null && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Port<>));
		}
	}
}
